/*
==========================================================
DX490a - Summer 2010
Instructor: Stelios Manousakis
==========================================================
Class ??:
Sets: Dictionaries & Environments
Contents:
• Sets
• Types of Dictionaries
- Dictionary
- IdentityDictionary
- TwoWayIdentityDictionary
- IdentitySet
- Environment
==========================================================
*/
// ================= SETS =================
// Throughout the year, we have dealt extensively with SequenceableCollections: Lists, Arrays, Wavetables, strings, etc. Sets are a parallel branch, stemming from the same root-branch, the abstract superclass 'Collection'/
// A Set is defined as a collection of objects, no two of which are equal. Whereas SequencableCollections are ordered, Sets are not always so.
// Here are the sub-branches of Collection that this file deals with (for the entire tree look at Collections:
....
// ====== TYPES OF DICTIONARIES ======
// ------ Dictionary --
// A Dictionary is an unordered collection mapping keys to values through association. Keys and values match if they are equal (i.e. == returns true).
d = Dictionary.new; //create a new dictionary
d.add(\entry_1 -> 10); // add an association of: symbol-or-string -> value
d.add(\entry_2 -> 23);
d.put(\entry_3, 31); // create, then add an association between two objects
d.put("entry_4", 44); // a string also works
d.put(5, 20); // a number as well
d; // see what's in there
// or like this:
d.postcs;
/*
Note:
.postcs can be very useful to look inside many objects: ex:
e = Env([0, 1, 0], [0.2, 0.8], -4);
e.postln;
// BUT:
e.postcs
*/
d.removeAt("entry_4"); // remove a key from the Dictionary
d[\entry_1]; // query the value of a key
d.at(\entry_1); // same thing, different syntax
d["entry_1"]; // this won't work: the key is a symbol, not a string
// • Iterating through a Dictionary
d.do({arg me, i; [me, i].postln;}); // passes in value and incrementer
d.keysDo({arg me, i; [me, i].postln}); // passes in keys and incrementer
d.keysValuesDo({arg key, val, i; [key, val, i].postln}); // passes in key, val and incrementer
d.findKeyForValue(10); // return the key associated with this value
// • Dictionaries of dictionaries
d.add(\entry_5 -> Dictionary[\a_node -> 1000, \a_controlBus -> 20, \a_group -> 2000])
// query the contens of the 2nd level dictionary:
d[\entry_5][\a_node]; // or:
d.at(\entry_5).at(\a_node); // same as with arrays, as this is a Collection nomenclature
// ------ IdentityDictionary --
// An IdentityDictionary is similar in all accounts, except that keys match only if they are identical objects (i.e. == returns true). This makes it faster than the Dictionary
// Notice this:
"aString" == "aString" // BUT
"aString" === "aString"
// whereas:
\aSymbol == \aSymbol
\aSymbol === \aSymbol
// and also:
2 == 2
2 === 2
// whereas:
a = Array.new
b = Array.new
a == b
a===b
// ------ TwoWayIdentityDictionary --
// TwoWayIdentityDictionary is similar to the IdentityDictionary, but it has the extra feature of allowing you to go from the key to the value as well as from the value to the key. However, it needs a bit more memory.
t = TwoWayIdentityDictionary.new; // create a new instance
t.put(\test, 999); // create and store an association
t.put(["some", "strings"], 1200); // yet another one
t.at(\test); // what is the value at this key?
t.getID(999); // and what is the key of this value?
t.getID(1200); // how about this value?
t.getID(888); // nil, as there is no such value
// ------ IdentitySet --
// An IdentitySet is to a Set what in IdentityDictionary is to a Dictionary: similar in all accounts but for it checking for identity instead of equality, which makes it faster.
// A handy trick: You can use IdentitySet to remove duplicates from an Array:
(
var e, f, g, h;
e = "such"; f = "fallacy"; g = "is"; h = "common";
a = [e, e, f, g, e, h, e];
);
a.as(IdentitySet); // convert to set
a.as(Set).as(Array); // and convert back
// ------ Environment --
// An Environment is an IdentityDictionary mapping Symbols to values; it has some with additional features and is commonly used as a storage 'name space' for creating, storing and accessing sets of persistent variables. In SC, there is always one Environment running, and this is were the global variables are stored. Upon startup, an Environment providing the memory space to store a universally accessible collection of named values, starting with '~'; this is the currentEnvironment.
// You can look at its state like this:
currentEnvironment;
// add a variable
~a_variable = 3.14;
// now check again:
currentEnvironment;
// ~a_variable = ... is actually a shortcut for this:
currentEnvironment.put(\a_variable, 3.14)
// and the accessing method
~a_variable;
// is a shortcut for:
currentEnvironment[\a_variable];
// You can create and Environment with the .make method and then fill it with values. This temporarily replaces the current Environment with a new one inside its scope.
// For example:
~a = 124
(
var a;
a = Environment.make({
~a = 100;
~b = 200;
~c = 300;
"inside the scope, ~a is: ".post;
~a.postln
});
a.postln;
"outside the scope, ~a is: ".post;
~a.postln
)
// You can use the contents of the Environment you created with the .use method and a function:
(
var a;
a = Environment.make({
~a = 10;
~b = 200;
~c = 3000;
"inside the scope, ~a is: ".post;
~a.postln
});
a.use({
~a + ~b + ~c;
"inside the function, ~a is: ".post;
~a.postln
}).postln;
"outside the scope, ~a still is: ".post;
~a.postln;
)
// you can also .use directly when creating the Environment:
(
var a;
a = Environment.use({
~a = 10;
~b = 200;
~c = 3000;
~a + ~b + ~c
}).postln;
)
// You can call a function inside an Environment evaluating it with the valueEnvir and valueArrayEnvir methods, to make it lookup any unspecified arguments from that environment; if there are any symbols with the same name there, then the function will use their values, if not it will just use the defaults.
// For example:
(
var f;
// define a function
f = { arg x, y, z; [x, y, z].postln; };
Environment.use({
~x = 7;
~y = 8;
~z = 9;
f.valueEnvir(1, 2, 3); // all values supplied
f.valueEnvir(1, 2); // z is looked up in the current Environment
f.valueEnvir(1); // y and z are looked up in the current Environment
f.valueEnvir; // all arguments are looked up in the current Environment
f.valueEnvir(z: 1); // x and y are looked up in the current Environment
});
Environment.use({
~x = 17;
~y = 18;
~z = 19;
f.valueEnvir(1, 2, 3); // all values supplied
f.valueEnvir(1, 2); // z is looked up in the current Environment
f.valueEnvir(1); // y and z are looked up in the current Environment
f.valueEnvir; // all arguments are looked up in the current Environment
f.valueEnvir(z: 1); // x and y are looked up in the current Environment
});
)
/* Let's look at a sound example from the Streams-Patterns-Events4 helpfile:
This is a somewhat contrived example of how the Environment might be used to manufacture SynthDefs.
Even though the three functions below have the freq, amp and pan args declared in different orders it does not matter, because valueEnvir looks them up in the environment.
*/
(
var a, b, c, n;
n = 40; // how many synths to create
// 3 different processes
a = { arg freq, amp, pan;
Pan2.ar(SinOsc.ar(freq), pan, amp)};
b = { arg amp, pan, freq;
Pan2.ar(RLPF.ar(Saw.ar(freq), freq * 6, 0.1), pan, amp)};
c = { arg pan, freq, amp;
Pan2.ar(Resonz.ar(GrayNoise.ar, freq * 2, 0.1), pan, amp * 2)};
// a task to automatically create the synthdefs
Task({
n.do({ arg i;
SynthDef("Help-SPE4-EnvirDef-" ++ i.asString, {
var out;
Environment.use({
// set values in the environment
~freq = exprand(80, 600);
~amp = 0.1 + 0.3.rand;
~pan = 1.0.rand2;
// call a randomly chosen instrument function
// with values from the environment
out = [a,b,c].choose.valueEnvir;
});
out = CombC.ar(out, 0.2, 0.2, 3, 1, out);
out = out * EnvGen.kr(
Env.sine, doneAction: 2, timeScale: 1.0 + 6.0.rand, levelScale: 0.3
);
Out.ar( 0, out );
}).send(s);
0.02.wait;
});
// play the synth
loop({
Synth( "Help-SPE4-EnvirDef-" ++ n.rand.asString );
(0.5 + 2.0.rand).wait;
});
}).play;
)